﻿/*********************************** Module Header ***********************************\
* Module Name:	TreeViewCrudDragDrop.xaml.cs
* Project:		MvvmTreeView
* Copyright (c) Microsoft Corporation.
* 
* Code Behind of Custom Silverlight User Control which implements a TreeView with added
* functionalities of CRUD and Drag-And-Drop
* 
* This source is subject to the Microsoft Public License.
* See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL.
* All other rights reserved.
* 
* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER 
* EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF 
* MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
\*************************************************************************************/

#region Using Directives

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using MvvmTreeView;
using SL4PopupMenu;
using System.ComponentModel;
using DragDropInterfaces = UIObjects.DragDropInterfaces;
using UIObjects.DragDropInterfaces.Extensions;
using System.Windows.Media.Imaging;
using Microsoft.Windows;
using System.Diagnostics;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Automation;

#endregion

namespace MvvmTreeView
{
    /// <summary>
    /// Code Behind of Custom Silverlight User Control which implements 
    /// a TreeView with added functionalities of CRUD and Drag-And-Drop
    /// </summary>
    public partial class WindowsStyleTreeView : UserControl
    {
        #region Member Variables

        /// <summary>
        /// Data bound to currently selected TreeViewItem
        /// </summary>
        private ITreeNode selectedNode;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the data bound to TreeView
        /// </summary>
        public ObservableCollection<ITreeNode> Nodes
        {
            get
            {
                return (ObservableCollection<ITreeNode>) TreeViewMain.ItemsSource;
            }
            set
            {
                TreeViewMain.ItemsSource = value;
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of TreeViewCrudDragDrop
        /// </summary>
        public WindowsStyleTreeView()
        {
            InitializeComponent();
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Event handler for MouseRightButtonDown of TreeView and TreeViewItem
        /// </summary>
        /// <param name="sender">Object on which event occurred</param>
        /// <param name="e">Event Arguements for the event</param>
        private void TreeViewMain_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            DisableEditForSelectedItem();

            if (sender is DependencyObject)
            {
                var content = (DependencyObject) sender as DependencyObject;
                DependencyObject parent = VisualTreeHelper.GetParent(content);

                while (!(parent is TreeViewItem))
                {
                    if (parent == null)
                    {
                        return;
                    }

                    parent = VisualTreeHelper.GetParent(parent);
                }

                var item = parent as TreeViewItem;
                var treeNode = (ITreeNode)item.GetItem();

                item.IsSelected = true;

                ResetNodeMenu(treeNode);
            }
 
            e.Handled = true;
        }

        /// <summary>
        /// Event handler for MouseRightButtonUp of TreeView and TreeViewItem
        /// </summary>
        /// <param name="sender">Object on which event occurred</param>
        /// <param name="e">Event Arguements for the event</param>
        private void TreeViewMain_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
        {
            DisableEditForSelectedItem();

            if (sender is TextBlock)
            {
                selectedNode = (ITreeNode)((sender as TextBlock).DataContext);
            }
            else
            {
                selectedNode = null;
            }

            ShowContextMenu(e);
        }

        /// <summary>
        /// Event handler for MouseLeftButtonDown of TreeView and TreeViewItem
        /// </summary>
        /// <param name="sender">Object on which event occurred</param>
        /// <param name="e">Event Arguements for the event</param>
        private void TreeViewMain_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            DisableEditForSelectedItem();

            HideContextMenu();
        }

        /// <summary>
        /// Event handler for Add Button Click Event
        /// </summary>
        /// <param name="sender">Add Button</param>
        /// <param name="e">Event arguements for event</param>
        private void AddButton_Click(object sender, RoutedEventArgs e)
        {
            HideContextMenu();
        }

        /// <summary>
        /// Event handler for Edit Button Click Event
        /// </summary>
        /// <param name="sender">Edit Button</param>
        /// <param name="e">Event arguements for event</param>
        private void EditButton_Click(object sender, RoutedEventArgs e)
        {
            EnalbleEditForSelectedItem();

            TreeViewItem selectedTreeViewItem = TreeViewExtensions.GetContainerFromItem(TreeViewMain, selectedNode);

            HideContextMenu();
        }

        /// <summary>
        /// Event handler for Delete Button Click Event
        /// </summary>
        /// <param name="sender">Delete Button</param>
        /// <param name="e">Event arguements for event</param>
        private void DeleteButton_Click(object sender, RoutedEventArgs e)
        {
            TreeViewItem selectedTreeViewItem =
                TreeViewExtensions.GetContainerFromItem(TreeViewMain, selectedNode);

            if (selectedTreeViewItem != null)
            {
                TreeViewItem selectedTreeViewItemParent = TreeViewExtensions.GetParentTreeViewItem(selectedTreeViewItem);

                ////if (selectedTreeViewItemParent != null)
                ////{
                ////    ITreeNode selectedParentNode = (ITreeNode)selectedTreeViewItemParent.DataContext;
                ////    selectedParentNode.Delete(selectedNode);
                ////}
                ////else
                ////{
                ////    objectTree.Remove(selectedNode);
                ////}
            }

            HideContextMenu();
        }

        #endregion

        #region Methods

        /// <summary>
        /// Show context menu
        /// </summary>
        /// <param name="e">Mouse Button Event Arguements for getting cursor position</param>
        private void ShowContextMenu(MouseButtonEventArgs e)
        {
            e.Handled = true;
            Point p = e.GetPosition(this);
        }

        /// <summary>
        /// Hide context menu
        /// </summary>
        private void HideContextMenu()
        {
        }

        /// <summary>
        /// Enable Edit Mode for selected TreeViewItem
        /// </summary>
        private void EnalbleEditForSelectedItem()
        {
            if (selectedNode != null)
            {
                SetTemplateForSelectedItem("TreeViewMainEditTemplate");
            }
        }

        /// <summary>
        /// Disable Edit mode for selected TreeViewItem
        /// </summary>
        private void DisableEditForSelectedItem()
        {
            if (selectedNode != null)
            {
                SetTemplateForSelectedItem("TreeViewMainReadTemplate");
                selectedNode = null;
            }
        }

        /// <summary>
        /// Set Template for Selected TreeViewItem
        /// </summary>
        /// <param name="templateName">Template Name</param>
        private void SetTemplateForSelectedItem(String templateName)
        {
            HierarchicalDataTemplate template = (HierarchicalDataTemplate)Resources[templateName];
            TreeViewItem selectedTreeViewItem = TreeViewExtensions.GetContainerFromItem(TreeViewMain, selectedNode);

            if (selectedTreeViewItem != null)
            {
                selectedTreeViewItem.HeaderTemplate = template;
            }
        }

        #endregion

        private void TreeViewMain_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            var treeNode = (ITreeNode)TreeViewMain.SelectedItem;

            if (treeNode != null)
            {
                ResetNodeMenu(treeNode);
                treeNode.OnSelect();
            }
        }

        private void ResetNodeMenu(ITreeNode treeNode)
        {
            var item = (TreeViewItem)TreeViewMain.GetContainerFromItem(treeNode);
            var content = item.GetContent();
            var contextMenu = treeNode.ContextMenu;

            if (content != null && contextMenu != null)
            {
                PopupMenuManager.Reset();

                var popupMenu = new PopupMenu
                {
                    Tag = contextMenu,
                    Foreground = new SolidColorBrush(Colors.Black),
                    OpenOnAccessKeyPressed = true
                };

                contextMenu.Tag = popupMenu;
                item.Triggers.Clear();

                PopupMenuManager.RegisterShortcut("Shift+M", popupMenu);

                AddContextMenuItems(popupMenu, contextMenu);

                popupMenu.AddTrigger(TriggerTypes.RightClick, item, content.NodeTextBlock, content.NodeImage, content.LayoutRoot);
            }
        }

        private void AddContextMenuItems(PopupMenu parentMenu, IContextMenuItem parentItem)
        {
            foreach (ContextMenuItem item in parentItem.Items)
            {
                if (item.Items.Count > 0)
                {
                    var popupMenu = new PopupMenu();

                    popupMenu.FocusOnShow = true;

                    var menuItem = parentMenu.AddSubMenu(popupMenu, item.Text, "/MvvmTreeView;component/Images/arrow.png", item.Tag, null, false, item.ClickHandler);

                    menuItem.ImageRightSource = new BitmapImage(new Uri("/MvvmTreeView;component/Images/arrow.png", UriKind.Relative));
                    menuItem.Tag = item.Tag;

                    AddContextMenuItems(popupMenu, item);
                }
                else if (item is CheckBoxContextMenuItem)
                {
                    var checkBoxItem = (CheckBoxContextMenuItem) item;
                    var checkBox = new CheckBox 
                    { 
                        Content = checkBoxItem.Text,
                        IsChecked = checkBoxItem.IsChecked,
                        Tag = item.Tag
                    };

                    var menuItem = parentMenu.AddItem(checkBox);

                    menuItem.Tag = item.Tag;
                    checkBox.Checked += (sender, e) => checkBoxItem.RaiseMenuCheckChanged(menuItem, new MenuCheckedEventArgs { IsChecked = true, Menu = parentMenu, MenuItem = menuItem, Checkbox = checkBox, CheckboxGroup = checkBoxItem.CheckBoxGroup });
                    checkBox.Unchecked += (sender, e) => checkBoxItem.RaiseMenuCheckChanged(menuItem, new MenuCheckedEventArgs { IsChecked = false, Menu = parentMenu, MenuItem = menuItem, Checkbox = checkBox, CheckboxGroup = checkBoxItem.CheckBoxGroup });

                    menuItem.KeyDown += (sender, e) =>
                    {
                        if (e.Key == Key.Space)
                        {
                            checkBox.IsChecked = !checkBox.IsChecked;
                        }
                    };
                }
                else
                {
                    var menuItem = parentMenu.AddItem(item.IconUrl, item.Text, item.Tag, item.ClickHandler);

                    menuItem.Tag = item.Tag;
                }
            }
        }

        void checkBox_KeyDown(object sender, KeyEventArgs e)
        {
            throw new NotImplementedException();
        }

        private void TreeNodeItem_NodeContentCreated(object sender, TreeNodeItemRoutedEventArgs e)
        {
            var item = (TreeViewItem)TreeViewMain.GetContainerFromItem(e.TreeNode);

            if (item != null)
            {
                var treeNode = e.TreeNode;
                var contextMenu = treeNode.ContextMenu;

                item.Margin = new Thickness(1, 2, 1, 2);
                item.Padding = new Thickness(1, 2, 1, 3);
                treeNode.ItemContent = e.TreeNodeContentItem;

                item.KeyDown += new KeyEventHandler(ItemKeyDown);
                item.Expanded += new RoutedEventHandler(ItemExpanded);

                treeNode.TreeViewItem = item;

                treeNode.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(TreeNode_PropertyChanged);
            }
        }

        private void TreeNode_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var treeNode = (ITreeNode)sender;

            if (e.PropertyName == "ContextMenu")
            {
                ResetNodeMenu(treeNode);
            }
        }

        private void ItemKeyDown(object sender, KeyEventArgs e)
        {
            var item = (TreeViewItem)sender;
            var treeNode = (ITreeNode)item.GetItem();

            if (e.Key == Key.Delete)
            {
                if (Keyboard.Modifiers == ModifierKeys.Shift || MessageBox.Show(string.Format("Are you sure you want to delete \"{0}\"?", treeNode.Text), "Delete?", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
                {
                    var parentItem = item.GetParentTreeViewItem();
                    TreeViewItem select = null;

                    if (parentItem != null)
                    {
                        var generator = parentItem.ItemContainerGenerator;
                        var index = generator.IndexFromContainer(item);

                        if (parentItem.Items.Count > index + 1)
                        {
                            select = generator.ContainerFromIndex(index + 1) as TreeViewItem;
                        }
                        else if (index > 0)
                        {
                            select = generator.ContainerFromIndex(index - 1) as TreeViewItem;
                        }
                        else
                        {
                            select = parentItem;
                        }
                    }

                    treeNode.Remove();

                    if (select != null)
                    {
                        select.Focus();
                    }
                    else if (TreeViewMain.Items.Count > 0)
                    {
                        select = (TreeViewItem) TreeViewMain.Items.Cast<ITreeNode>().Single().TreeViewItem;

                        select.Focus();
                    }
                    else
                    {
                        TreeViewMain.Focus();
                    }
                }

                e.Handled = true;
            }
            else if (e.Key == Key.F2 && treeNode.AllowRename)
            {
                var content = item.GetContent();

                content.EditMode = true;

                e.Handled = true;
            }
        }

        // intended for target
        private void TreeViewDragDropTarget_DragEnter(object sender, Microsoft.Windows.DragEventArgs e)
        {
            try
            {
                var node = this.DragDropTarget.GetDropTargetNode(e);

                if (node != null && node.AllowDrop)
                {
                    var eventArgs = e.Convert();

                    Debug.WriteLine("Drag entering: " + node.Text);

                    node.DragEnter(sender, eventArgs);

                    e.ConvertBack(eventArgs);
                }
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }
        }

        // intended for target
        private void TreeViewDragDropTarget_DragLeave(object sender, Microsoft.Windows.DragEventArgs e)
        {
            try
            {
                var node = this.DragDropTarget.GetDropTargetNode(e);

                if (node != null && node.AllowDrop)
                {
                    var eventArgs = e.Convert();

                    Debug.WriteLine("Drag leaving: " + node.Text);

                    node.DragLeave(sender, eventArgs);

                    e.ConvertBack(eventArgs);
                }
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }
        }

        // intended for target
        private void TreeViewDragDropTarget_DragOver(object sender, Microsoft.Windows.DragEventArgs e)
        {
            try
            {
                var node = this.DragDropTarget.GetDropTargetNode(e);

                if (node != null && node.AllowDrop)
                {
                    var eventArgs = e.Convert();

                    Debug.WriteLine("Drag over: " + node.Text);

                    node.DragOver(sender, eventArgs);

                    e.ConvertBack(eventArgs);
                }
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }
        }

        // intended for source (this determines visual state)
        private void DragDropTarget_GiveFeedback(object sender, Microsoft.Windows.GiveFeedbackEventArgs e)
        {
            try
            {
                var item = e.OriginalSource as TreeViewItem;

                if (item != null)
                {
                    var node = (ITreeNode)item.GetItem();

                    if (node != null)
                    {
                        var eventArgs = e.Convert();

                        Debug.WriteLine("Give feedback: " + node.Text);

                        node.GiveFeedback(sender, eventArgs);

                        e.ConvertBack(eventArgs);
                    }
                }
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }
        }

        // intended for source
        private void DragDropTarget_ItemDragStarting(object sender, ItemDragEventArgs e)
        {
            try
            {
                var items = (SelectionCollection)e.Data;
                var node = (ITreeNode)items[0].Item;

                var eventArgs = e.Convert();

                Debug.WriteLine("Drag starting from: " + node.Text);

                node.DragStarting(sender, eventArgs);

                e.ConvertBack(eventArgs);

                var dragImage = (Image)e.DragDecoratorContent;
            
                dragImage.Source = new WriteableBitmap((TreeNodeItemContent)node.ItemContent, null);
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }
        }

        private void DragDropTarget_ItemDragCompleted(object sender, ItemDragEventArgs e)
        {
            try
            {
                var items = (SelectionCollection)e.Data;
                var node = (ITreeNode)items[0].Item;
                var eventArgs = e.Convert();

                Debug.WriteLine("Drag completed from: " + node.Text);

                node.DragCompleted(sender, eventArgs);

                e.ConvertBack(eventArgs);
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }
        }

        private void DragDropTarget_ItemDroppedOnSource(object sender, Microsoft.Windows.DragEventArgs e)
        {
            Debugger.Break();
        }

        private void DragDropTarget_ItemDroppedOnTarget(object sender, ItemDragEventArgs e)
        {
            try
            {
                var node = this.DragDropTarget.GetDropTargetNode(e);

                if (node != null)
                {
                    var index = this.DragDropTarget.GetInsertionIndex(e);

                    Debug.WriteLine("Drag dropped on: " + node.Text + " at index: " + index.ToString());

                    var eventArgs = e.Convert();

                    node.ItemDropped(sender, eventArgs, index);

                    e.ConvertBack(eventArgs);
                }
            }
            catch (Exception ex)
            {
                Debugger.Break();
            }
        }

        private void DragDropTarget_QueryContinueDrag(object sender, QueryContinueDragEventArgs e)
        {
            //e.Action = DragAction.Continue;
            //e.Handled = true;
        }

        private void ItemExpanded(object sender, RoutedEventArgs e)
        {
            var item = (TreeViewItem)sender;
            var treeNode = (ITreeNode)item.GetItem();

            treeNode.Expand();
        }

        private void TreeViewMain_Loaded(object sender, RoutedEventArgs e)
        {
        }

        public TreeView InternalTreeView
        {
            get
            {
                return TreeViewMain;
            }
        }
    }
}
